#define MXMODULE_DRS
#define MXMODULE_REGION
#define MXMODULE_VECTOR

#include "degfx/degfx.h"
#include <string.h>
#include <assert.h>

static MX_REF(MX_BITMAP) mx__bufferref;
static unsigned int mx__stopsession = 0;

MX_FONTREF mx__fontref;

void mx_font_default(MX_FONT * font)
{
	if (font == MXREF(mx__fontref))
		return;

		/** The DEGFX system takes over ownership of the specified font.  This is
        done by creating a reference to the specified font marking the font
        for deletion.  So the font is guaranteed to be deleted as necessary
        whevener no objects have references to the font and a new default font
        is set. */
	if (MXREF(mx__fontref)) {
		mx_delete(MXREF(mx__fontref));
		mx_unref(&mx__fontref);
	}

	if (font)
		mx_ref(&mx__fontref, font);
	else
		memset(&mx__fontref, 0, sizeof(mx__fontref));
}

static void mx__exit(void)
{
	mx_font_default(0);
	mx_gfx_stop();
}

/** !Start DEGFX and set the graphics mode */
unsigned mx_gfx_start(MX_GFX_ARGS * userargs)
{
	MX_GFX_ARGS args = mx__args;
	const MX_GFX_ARGS old = mx__args;

	if (userargs) {
		args = *userargs;

				/** If the requested mode has same size, depth, title and driver as
            the previous one then the graphics mode is not changed. */
		if ((old.session) && (args.w == old.w) && (args.h == old.h)
			&& (args.c == old.c) && (args.title == old.title)
			&& (((args.driver) && (args.driver == old.driver))
				|| (!args.driver)))
			goto done_mode_change;

		/** If the arguments are 0 then default values are used. */
	} else if (old.session == mx__stopsession)
		memset(&args, 0, sizeof(args));

		/** A NULL driver means use the current driver or if there is none, then
        use the default driver. */
	if (!args.driver)
		args.driver = mx__args.driver;
	if (!args.driver)
		args.driver = &MX__DRIVER_DEFAULT;

	/* If the driver will been changed, then stop the old one */
	if ((old.driver) && (old.driver->_stop) && (args.driver != old.driver))
		old.driver->_stop();

		/** Function returns 0 if gfx mode set failed. */
	if (!args.driver->_start(&args))
		return false;

  done_mode_change:

		/** After successful mode set the screen area contains the screen dimensions. */
	args.screen.x1 = 0;
	args.screen.y1 = 0;
	args.screen.x2 = args.w - 1;
	args.screen.y2 = args.h - 1;

		/** If a redraw buffer is specified any previous redraw buffer that was
        allocated will be deleted. */
	if ((old.buffer) && (old.buffer != args.buffer)) {
		mx_delete(old.buffer);
		mx_unref(&mx__bufferref);
	}

		/** If no redraw buffer specified then one will be allocated and locked. */
	if (!args.buffer) {
		args.buffer = mx_bitmap(MX_GFX_BUFFER_W, MX_GFX_BUFFER_H);
		MXDEBUG_ATOMNAME(args.buffer, "buffer_default");
	}
	assert(args.buffer);
	mx_ref(&mx__bufferref, args.buffer);

	if (mx__args.session == 0)
		atexit(mx__exit);

		/** The session is incremented after every successfull mode set. */
	mx__args = args;
	mx__args.session = mx__stopsession + 1;

		/** The MX_GFX_ARGS struct (if present) is modified to the mode information
       actually set by the underlying driver. */
	if (userargs)
		*userargs = *mx_gfx_info();

	return true;
}

/** !Stop DEGFX
This function stops the graphics mode and (depending on the driver)
probably returns to a text mode.  It can be called before the program return
to the operating system however if the user does not do this it will be
done automatically.  */
void mx_gfx_stop(void)
{
		/** The internal graphics redraw buffer is released. */
	if (mx__args.buffer) {
		/* Flag buffer for deletion, wont actually free because we have a
		   lock. */
		mx_delete(mx__args.buffer);

		/* Unlock and the buffer will be freed if no other locks exist. */
		mx_unref(&mx__bufferref);
	}

	if ((mx__args.driver) && (mx__args.driver->_stop))
		mx__args.driver->_stop();

	if (mx__args.session)
		mx__stopsession = mx__args.session;

	memset(&mx__args, 0, sizeof(MX_GFX_ARGS));
}

/** This function returns information about the current graphics mode. */
const MX_GFX_ARGS *mx_gfx_info(void)
{
	return &mx__args;
}

/** This function sets the redraw callback. */
void mx_gfx_redraw(MX_REDRAW_FUNC redraw)
{
	mx__args.redraw = redraw;

	mx_gfx_dirty(&mx__args.screen);
}

#if !defined(MX__DRIVER_WIN32_GDI)
#   define MX__MOUSE_W   10
#   define MX__MOUSE_H   16
#   define MX__0 MXRGBT(0, 0, 0, 255)
#   define MX__1 MXRGBT(0, 0, 0, 0)
#   define MX__2 MXRGBT(255, 255, 255, 0)

static MX_PIXEL mx__pointer_data[MX__MOUSE_W * MX__MOUSE_H] = {
	MX__1, MX__1, MX__0, MX__0, MX__0, MX__0, MX__0, MX__0, MX__0, MX__0,
	MX__1, MX__2, MX__1, MX__0, MX__0, MX__0, MX__0, MX__0, MX__0, MX__0,
	MX__1, MX__2, MX__2, MX__1, MX__0, MX__0, MX__0, MX__0, MX__0, MX__0,
	MX__1, MX__2, MX__2, MX__2, MX__1, MX__0, MX__0, MX__0, MX__0, MX__0,
	MX__1, MX__2, MX__2, MX__2, MX__2, MX__1, MX__0, MX__0, MX__0, MX__0,
	MX__1, MX__2, MX__2, MX__2, MX__2, MX__2, MX__1, MX__0, MX__0, MX__0,
	MX__1, MX__2, MX__2, MX__2, MX__2, MX__2, MX__2, MX__1, MX__0, MX__0,
	MX__1, MX__2, MX__2, MX__2, MX__2, MX__2, MX__2, MX__2, MX__1, MX__0,
	MX__1, MX__2, MX__2, MX__2, MX__2, MX__2, MX__2, MX__2, MX__2, MX__1,
	MX__1, MX__2, MX__2, MX__2, MX__2, MX__2, MX__1, MX__1, MX__1, MX__0,
	MX__1, MX__2, MX__2, MX__1, MX__2, MX__2, MX__1, MX__0, MX__0, MX__0,
	MX__1, MX__2, MX__1, MX__0, MX__1, MX__2, MX__2, MX__1, MX__0, MX__0,
	MX__0, MX__1, MX__0, MX__0, MX__1, MX__2, MX__2, MX__1, MX__0, MX__0,
	MX__0, MX__0, MX__0, MX__0, MX__0, MX__1, MX__2, MX__2, MX__1, MX__0,
	MX__0, MX__0, MX__0, MX__0, MX__0, MX__1, MX__2, MX__2, MX__1, MX__0,
	MX__0, MX__0, MX__0, MX__0, MX__0, MX__0, MX__1, MX__1, MX__0, MX__0
};

#   undef MX__0
#   undef MX__1
#   undef MX__2

static MX_BITMAP mx__pointer = MXBITMAP_DECLARE(mx__pointer_data, MX__MOUSE_W, MX__MOUSE_H);
#endif

void mx__gfx_flush(MX_DRAW_FUNC draw, MX_RECT * rect)
{
	MX_LOCK flushlock;

	assert(rect);

	assert(MXRECT_VALID(*rect));
	assert(mx__args.buffer);
	assert(mx__args.driver);
	assert(mx__args.redraw);
	assert(draw);

	mx_lock(&flushlock, mx__args.buffer);

	/* Make sure the area isnt too large and has the correct alignment */
	mx_bitmap_offset(mx__args.buffer, rect->x1, rect->y1);
	MXRECT_INTERSECT(*MXRECT(mx__args.buffer), *rect, *rect);

	/* Actually get the bitmap to be updated */
	mx_bitmap_clip(mx__args.buffer, rect);
	mx__args.redraw(rect);

	/* Draw the pointer */
	mx_bitmap_clip(mx__args.buffer, rect);
#if !defined(MX__DRIVER_WIN32_GDI)
	if ((!mx__args.pointer) && (!mx__args._hidepointer))
		mx_blit(&mx__pointer, 0, 0, mx__mousex, mx__mousey, MXDEFAULT, MXDEFAULT);
#endif

	/* Push the buffer to the physical screen */
	draw(rect, mx__args.buffer->_array, mx_w(mx__args.buffer) + 1);

	mx_unlock(&flushlock);
}

void mx__gfx_update(MX_DRAW_FUNC draw, const MX_RECT * rect)
{
	int size;
	MX_REGION region;
	MX_RECT temp;

	assert(rect);

	temp = *rect;
	MXRECT_INTERSECT(mx__args.screen, temp, temp);
	if (!MXRECT_VALID(temp))
		return;

	mx_vector(&region);
	mx_vector_reserve(&region, 64);
	mx_vector_append(&region, &temp, 1);

	while ((size = mx_vector_size(&region)) != 0) {
		temp = region.data[size - 1];

		mx__gfx_flush(draw, &temp);

		mx_region_clip(&region, &temp);
	}

	mx_vector_free(&region);
}

/** !Mark part of the screen as dirty 
This function marks a portion of the screen as dirty. */
void mx_gfx_dirty(const MX_RECT * rect)
{
	if (!rect)
		rect = &mx__args.screen;

	mx__args.driver->_dirty(rect);
}

/** !Poll the DEGFX grpahics/keyboard/mouse system
This function causes the conditions of the keyboard and mose to be read
from the system.  This should be done every time you want to read the keyboard
of the mouse.  Most drivers also redraw the screen during the poll. */
unsigned mx_gfx_poll(void)
{
	return mx__args.driver->_poll();
}

/** !Get the mouse position and state
This function reads the mouse pointer position and button state.  The
function return non-zero if a new position has been read. */
unsigned mx_gfx_pointer(int *x, int *y, int *b)
{
	unsigned ret = 0;

#if !defined(MX__DRIVER_WIN32_GDI)
	const int oldx = mx__mousex;
	const int oldy = mx__mousey;
#endif

	if (mx__args.session == mx__stopsession)
		return false;

	/* Pass the request to the pointer driver */
	if (mx__args.driver->_pointer)
		ret = mx__args.driver->_pointer();
	if (!ret)
		return false;

		/** If arguments are 0 then they are ignored. */
	if (x)
		*x = mx__mousex;
	if (y)
		*y = mx__mousey;
	if (b)
		*b = mx__mouseb;

#if !defined(MX__DRIVER_WIN32_GDI)
	if ((!mx__args.pointer)
		&& ((mx__mousex != oldx) || (mx__mousey != oldy))) {
		MX_RECT prev, rect;

		prev.x1 = oldx;
		prev.y1 = oldy;
		prev.x2 = oldx + mx_w(&mx__pointer);
		prev.y2 = oldy + mx_h(&mx__pointer);

		rect.x1 = mx__mousex;
		rect.y1 = mx__mousey;
		rect.x2 = mx__mousex + mx_w(&mx__pointer);
		rect.y2 = mx__mousey + mx_h(&mx__pointer);

		/* If mouse positions overlap, draw them together */
		if (MXRECTS_OVERLAP(prev, rect)) {
			MXRECT_UNION(prev, rect, rect);
			mx_gfx_dirty(&rect);

			/* Mouse moved a great distance, draw each position seperately */
		} else {
			mx_gfx_dirty(&rect);
			mx_gfx_dirty(&prev);
		}
	}
#endif
	return true;
}

/** !Hide the pointer
This only works if DEGFX draws the pointer.  This function does nothing on platforms that draw
thier own pointer i.e. Win32GDI. */

unsigned mx_gfx_hidepointer(unsigned hide)
{
	const unsigned old = mx__args._hidepointer;

	  /** Unlike many other graphics libraries you dont have to hide the pointer
      when drawing on the screen.  This is not necessary with DEGFX because drawing
      takes place on an internal redraw buffer and the mouse is only added when
      blitting the innternal redraw buffer to the screen. */
	mx__args._hidepointer = hide;

	return old;
}

/** !Return keypress information 
If new keypress information is available the function return non-zero, otherwise no key has been pressed. */
unsigned mx_gfx_key(int *scan, int *ascii)
{
	int dummy;

	if (!mx__args.driver->_key)
		return 0;

		/** If arguments are 0 then they are ignored. */
	if (!scan)
		scan = &dummy;
	if (!ascii)
		ascii = &dummy;

	return mx__args.driver->_key(scan, ascii);
}
